﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;

namespace SSG.Tools
{
    /// <summary>
    ///   Tools for File handling.
    /// </summary>
    public static class FileTools
    {
        public static readonly string DirectorySeperator = new string(Path.DirectorySeparatorChar, 1);

        /// <summary>
        ///   Is <paramref name = "fileNameCandidate" /> a valid file name?
        /// </summary>
        /// <param name = "fileNameCandidate"></param>
        /// <returns></returns>
        public static bool IsValidFileName(this string fileNameCandidate)
        {
            if (String.IsNullOrEmpty(fileNameCandidate)) return false;
            char[] illegal = Path.GetInvalidFileNameChars();
            return fileNameCandidate.IndexOfAny(illegal) < 0;
        }

        /// <summary>
        ///   Is <paramref name = "pathCandidate" /> a valid file name?
        ///   Important: Does not check whether the file exists!
        /// </summary>
        /// <param name = "pathCandidate"></param>
        /// <returns></returns>
        /// <seealso cref = "File.Exists" />
        public static bool IsValidPath(this string pathCandidate)
        {
            if (String.IsNullOrEmpty(pathCandidate)) return false;
            char[] illegal = Path.GetInvalidPathChars();
            return pathCandidate.IndexOfAny(illegal) < 0;
        }

        /// <summary>
        ///   Get (and if necessary create the directory for the user data.
        /// </summary>
        /// <returns></returns>
        public static string GetUserDataPath(string application)
        {
            if (String.IsNullOrEmpty(application)) throw new ArgumentNullException("application");
            Contract.EndContractBlock();

            string dir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            dir = Path.Combine(dir, application);
            if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
            return dir;
        }

        /// <summary>
        ///   Write a <see cref = "DataTable" /> to a binary file.
        /// </summary>
        /// <param name = "fileName"></param>
        /// <param name = "dataTable"></param>
        public static void WriteDataTableToBinaryFile(string fileName, DataTable dataTable)
        {
            FileStream fs = new FileStream(fileName, FileMode.Create);
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(fs, dataTable);
            fs.Close();
        }

        /// <summary>
        ///   Read a <c>DataTable</c> from a binary file
        /// </summary>
        /// <param name = "fileName"></param>
        /// <returns></returns>
        public static DataTable ReadDataTableFromBinaryFile(string fileName)
        {
            FileStream fs = new FileStream(fileName, FileMode.Open);
            BinaryFormatter bf = new BinaryFormatter();
            DataTable dt = (DataTable) bf.Deserialize(fs);
            fs.Close();
            return dt;
        }

        /// <summary>
        ///   Information about a dll based on <c>FileVersionInfo</c>. Checks absolute path, 
        ///   the assembly's path, and <c>windows/system32</c>.
        /// </summary>
        /// <param name = "nameOnlyOrPathAndName"></param>
        /// <param name = "assembly">Assembly for searching in directory of assembly, can be <c>null</c></param>
        /// <param name = "dllName"></param>
        /// <param name = "path"></param>
        /// <param name = "version"></param>
        /// <returns></returns>
        /// <seealso cref = "FileVersionInfo" />
        /// <seealso cref = "RuntimeTools.GetLoadedModule(string,out string,out string,out string)" />
        //  EXTERNAL: http://msdn.microsoft.com/en-us/library/ms682586(v=VS.85).aspx
        //  IMPROVE: Searching a DLL, SetDllDirectory support when searching still missing
        public static bool GetAssemblyInformation(this string nameOnlyOrPathAndName, Assembly assembly, out string dllName, out string path, out string version)
        {
            path = version = dllName = null;
            if (String.IsNullOrEmpty(nameOnlyOrPathAndName)) return false;

            // defaults
            string fileNameOnly = dllName = Path.GetFileName(nameOnlyOrPathAndName); // set a default
            FileVersionInfo fvi = null;

            // check for absolutely specified dll
            if (Path.IsPathRooted(nameOnlyOrPathAndName)) {
                // absolute path, check this particular file
                if (File.Exists(nameOnlyOrPathAndName)) fvi = FileVersionInfo.GetVersionInfo(nameOnlyOrPathAndName);
                if (fvi != null) path = Path.GetDirectoryName(nameOnlyOrPathAndName);
            }

            // search order: http://msdn.microsoft.com/en-us/library/ms682586(v=VS.85).aspx
            List<string> searchPaths = new List<string>();
            Assembly a = assembly ?? typeof (FileTools).Assembly;

            // ReSharper disable ConditionIsAlwaysTrueOrFalse
            if (a != null && a.Location != null) searchPaths.Add(Path.GetDirectoryName(a.Location));
            searchPaths.Add(Environment.SystemDirectory); // e.g. c:\windows\system32
            searchPaths.Add(Environment.ExpandEnvironmentVariables("%WINDIR%")); // e.g. c:\windows\
            // ReSharper restore ConditionIsAlwaysTrueOrFalse

            if (!String.IsNullOrEmpty(fileNameOnly)) {
                foreach (string searchPath in searchPaths) {
                    string assemblyFile = Path.Combine(searchPath, fileNameOnly);
                    if (File.Exists(assemblyFile)) fvi = FileVersionInfo.GetVersionInfo(assemblyFile);
                    if (fvi == null) continue;
                    path = searchPath;
                    break;
                } // search for all paths
            }

            // still not found
            if (fvi == null) return false;

            // get info
            dllName = StringTools.FirstNotEmptyValue(fvi.OriginalFilename, dllName); // fvi.FileName contains path
            version = StringTools.FirstNotEmptyValue(fvi.ProductVersion, fvi.FileVersion);
            path = StringTools.FirstNotEmptyValue(Path.GetDirectoryName(fvi.FileName), path);
            return true;
        }

        /// <summary>
        ///   Smart detection of the path.
        /// </summary>
        /// <param name = "candidate"></param>
        /// <param name = "endWithDirectorySeperator"></param>
        /// <returns></returns>
        public static string GetDirectoryPathSmartly(this string candidate, bool endWithDirectorySeperator)
        {
            if (String.IsNullOrEmpty(candidate)) return null;

            // fast detecion, path end with "/"
            if (candidate.EndsWith(DirectorySeperator))
                return (endWithDirectorySeperator) ? candidate : candidate.Substring(0, candidate.Length - 1);

            // determine path
            string p;
            if (File.Exists(candidate)) {
                // clearly a local file
                p = Path.GetDirectoryName(candidate);
            }
            else if (Directory.Exists(candidate)) {
                // clearly a local directory
                p = candidate;
            }
            else {
                // extension
                string e = Path.GetExtension(candidate);
                // e.g. ".txt", we expect at least a 2-char extens such as ".cs"
                if (!String.IsNullOrEmpty(e) && e.Length > 2)
                    p = Path.GetDirectoryName(candidate);
                else
                    p = candidate; // we assume the last part is not a file, but the last directory segment;               
            }

            if (p == null) throw new Exception("Illegal state, path is null");
            if (p.EndsWith(DirectorySeperator))
                p = (endWithDirectorySeperator) ? p : p.Substring(0, p.Length - 1);
            else
                p = (endWithDirectorySeperator) ? p + DirectorySeperator : p;

            return p;
        }

        /// <summary>
        /// Read a text file
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static string ReadTextFile(string file)
        {
            if (String.IsNullOrEmpty(file)) return null;
            string content;
            using (TextReader tr = new StreamReader(file))
            {
                content = tr.ReadToEnd();
                tr.Close();
            }
            return content;
        }
    }
}